<!DOCTYPE html>
<html>
<head>
    <title>语音识别WebSocket客户端示例</title>
    <meta charset="UTF-8">
    <style>
        body { 
            font-family: Arial, sans-serif; 
            margin: 20px; 
            max-width: 800px;
            margin: 0 auto;
            padding: 20px;
        }
        h1 { color: #333; }
        button { 
            padding: 10px 20px; 
            background-color: #4CAF50; 
            border: none; 
            color: white; 
            cursor: pointer;
            border-radius: 5px;
            font-size: 16px;
            margin: 5px;
        }
        button:hover { background-color: #45a049; }
        button:disabled { background-color: #cccccc; }
        #status { 
            font-weight: bold; 
            margin: 10px 0;
        }
        #resultText { 
            margin-top: 20px; 
            padding: 10px; 
            border: 1px solid #ddd; 
            min-height: 100px;
            border-radius: 5px;
            white-space: pre-wrap;
            background-color: white;
            color: #333;
            font-family: inherit;
            font-size: inherit;
            width: 100%;
            box-sizing: border-box;
            resize: vertical;
        }
        .speaking { color: green; }
        .not-speaking { color: gray; }
        
        /* 进度条样式 */
        .progress-container {
            width: 100%;
            background-color: #f1f1f1;
            margin: 10px 0;
            height: 20px;
            border-radius: 10px;
            overflow: hidden;
        }
        .progress-bar {
            height: 100%;
            background-color: #4CAF50;
            width: 0%;
            transition: width 0.1s;
        }
        
        .history-container {
            margin-top: 20px;
            max-height: 300px;
            overflow-y: auto;
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
        }
        
        .history-item {
            padding: 5px;
            border-bottom: 1px solid #eee;
        }
        
        .history-item:last-child {
            border-bottom: none;
        }
        
        .log-container {
            margin-top: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
            padding: 10px;
            max-height: 200px;
            overflow-y: auto;
            background-color: #f9f9f9;
        }
        
        .log-entry {
            margin: 2px 0;
            font-family: monospace;
            font-size: 14px;
        }
        
        .settings {
            margin: 20px 0;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 5px;
            background-color: #f9f9f9;
        }
        
        .settings label {
            display: block;
            margin: 10px 0 5px 0;
        }
        
        .settings input, .settings select {
            padding: 5px;
            width: 100%;
            box-sizing: border-box;
        }
        
        .copy-btn {
            padding: 5px 10px;
            font-size: 12px;
            margin-top: 5px;
            background-color: #f0f0f0;
            color: #333;
            border: 1px solid #ddd;
        }
        
        .copy-btn:hover {
            background-color: #e0e0e0;
        }
    </style>
</head>
<body>
    <h1>语音识别WebSocket客户端示例</h1>
    
    <div class="settings">
        <h3>连接设置</h3>
        <label for="serverUrl">服务器地址:</label>
        <input type="text" id="serverUrl" value="ws://localhost:8000/ws/audio">
        
        <label for="langSym">语言代码:</label>
        <select id="langSym">
            <option value="zh" selected>中文 (zh)</option>
            <option value="en">英语 (en)</option>
        </select>
        
        <label for="autoSilenceDetection">自动静音检测:</label>
        <select id="autoSilenceDetection">
            <option value="false" selected>关闭</option>
            <option value="true">开启</option>
        </select>
        
        <button id="connectBtn" onclick="connectWebSocket()">连接服务器</button>
        <button id="disconnectBtn" onclick="disconnectWebSocket()" disabled>断开连接</button>
    </div>
    
    <div>
        <h3>录音控制</h3>
        <button id="toggleBtn" onclick="toggleRecording()" disabled>开始录音</button>
        <p>状态: <span id="status">未连接</span></p>
        <p id="volumeIndicator" class="not-speaking">音量: 0.00</p>
    </div>
    
    <!-- 静音进度条 -->
    <div id="silenceContainer" style="display: none;">
        <div>2秒静音倒计时: <span id="silenceCountdown">0.0</span>秒</div>
        <div class="progress-container">
            <div id="silenceProgress" class="progress-bar"></div>
        </div>
    </div>
    
    <div>
        <h3>识别结果: <button onclick="copyResult()" class="copy-btn">复制结果</button></h3>
        <textarea id="resultText">等待识别结果...</textarea>
    </div>
    
    <div>
        <h3>历史记录:</h3>
        <button onclick="clearHistory()" style="padding: 5px; font-size: 12px;">清除历史</button>
        <div id="historyContainer" class="history-container">
            <!-- 历史记录将在这里显示 -->
        </div>
    </div>
    
    <div>
        <h3>通信日志:</h3>
        <button onclick="clearLog()" style="padding: 5px; font-size: 12px;">清除日志</button>
        <div id="logContainer" class="log-container">
            <!-- 通信日志将在这里显示 -->
        </div>
    </div>
    
    <script>
        let ws;
        let recognitionHistory = [];
        let isRecording = false; // 添加录音状态标志
        
        function connectWebSocket() {
            // 获取服务器地址
            const serverUrlInput = document.getElementById('serverUrl').value;
            
            // 直接使用用户输入的WebSocket地址
            const serverUrl = serverUrlInput;
            
            addLogEntry(`连接到WebSocket: ${serverUrl}`, "black");
            
            try {
                // 创建WebSocket连接
                ws = new WebSocket(serverUrl);
                
                // 连接打开时的处理
                ws.onopen = function() {
                    document.getElementById('status').innerText = "已连接";
                    document.getElementById('toggleBtn').disabled = false;
                    document.getElementById('connectBtn').disabled = true;
                    document.getElementById('disconnectBtn').disabled = false;
                    
                    addLogEntry("WebSocket连接已建立", "green");
                };
                
                // 收到消息时的处理
                ws.onmessage = function(event) {
                    const data = JSON.parse(event.data);
                    addLogEntry("收到: " + JSON.stringify(data), "blue");
                    
                    if (data.status === "ready" || data.status === "recording" || 
                        data.status === "calibrating" || data.status === "listening" || 
                        data.status === "silence_detected" || data.status === "no_voice" || 
                        data.status === "recognizing" || data.status === "processing") {
                        document.getElementById('status').innerText = data.message;
                        
                        // 更新录音状态
                        if (data.status === "recording" || data.status === "calibrating" || 
                            data.status === "listening") {
                            isRecording = true;
                            
                            // 检查消息中是否包含自动静音检测的信息
                            if (data.message && data.message.includes("自动静音检测")) {
                                // 更新自动静音检测的显示状态
                                const isAutoSilenceEnabled = data.message.includes("开启");
                                document.getElementById('silenceContainer').style.display = isAutoSilenceEnabled ? "block" : "none";
                            }
                        }
                        
                        // 当停止录音时，自动切换按钮状态
                        if (data.status === "no_voice" || data.status === "recognizing" || 
                            data.status === "processing" || 
                            (data.message && (data.message.includes("停止录音") || data.message.includes("识别完成")))) {
                            isRecording = false;
                            document.getElementById('toggleBtn').innerText = "开始录音";
                            // 隐藏静音倒计时
                            document.getElementById('silenceContainer').style.display = "none";
                            // 重置静音进度条
                            resetSilenceProgress();
                        }
                    } else if (data.status === "volume") {
                        updateVolumeIndicator(data);
                        
                        // 只有在录音状态且启用了自动静音检测才更新静音进度条
                        if (isRecording) {
                            // 更新静音进度条
                            if (!data.is_speaking && data.silent_count !== undefined && data.max_silent !== undefined) {
                                const silentSeconds = (data.silent_count / data.max_silent * 2).toFixed(1); // 计算实际秒数
                                const percentage = (data.silent_count / data.max_silent) * 100;
                                
                                document.getElementById('silenceProgress').style.width = `${percentage}%`;
                                document.getElementById('silenceCountdown').innerText = silentSeconds;
                            } else {
                                // 如果正在说话，重置静音进度条
                                resetSilenceProgress();
                            }
                        }
                    } else if (data.status === "success") {
                        // 识别成功
                        isRecording = false;
                        document.getElementById('resultText').value = data.text;
                        document.getElementById('status').innerText = "识别完成";
                        document.getElementById('toggleBtn').innerText = "开始录音";
                        
                        // 重置静音进度条
                        resetSilenceProgress();
                        
                        // 添加到历史记录
                        const currentTime = new Date().toLocaleTimeString();
                        recognitionHistory.push({
                            time: currentTime,
                            text: data.text,
                            inference_time: data.inference_time
                        });
                        
                        // 更新历史记录显示
                        updateHistoryDisplay();
                        
                    } else if (data.status === "no_valid_audio") {
                        isRecording = false;
                        document.getElementById('resultText').value = "未检测到有效语音";
                        document.getElementById('status').innerText = data.message;
                        document.getElementById('toggleBtn').innerText = "开始录音";
                        
                        // 重置静音进度条
                        resetSilenceProgress();
                        
                    } else if (data.status === "error") {
                        isRecording = false;
                        document.getElementById('status').innerText = "错误: " + data.message;
                        document.getElementById('toggleBtn').innerText = "开始录音";
                        addLogEntry("错误: " + data.message, "red");
                        alert("发生错误: " + data.message);
                        
                        // 重置静音进度条
                        resetSilenceProgress();
                    }
                };
                
                // 连接关闭时的处理
                ws.onclose = function() {
                    isRecording = false;
                    document.getElementById('status').innerText = "连接已断开";
                    document.getElementById('toggleBtn').disabled = true;
                    document.getElementById('connectBtn').disabled = false;
                    document.getElementById('disconnectBtn').disabled = true;
                    
                    // 重置静音进度条
                    resetSilenceProgress();
                    
                    addLogEntry("WebSocket连接已关闭", "orange");
                };
                
                // 连接错误时的处理
                ws.onerror = function(error) {
                    isRecording = false;
                    document.getElementById('status').innerText = "WebSocket错误";
                    addLogEntry("WebSocket错误", "red");
                    console.error('WebSocket错误:', error);
                    
                    // 重置静音进度条
                    resetSilenceProgress();
                };
            } catch (e) {
                isRecording = false;
                addLogEntry("连接错误: " + e.message, "red");
                alert("连接错误: " + e.message);
                
                // 重置静音进度条
                resetSilenceProgress();
            }
        }
        
        function resetSilenceProgress() {
            document.getElementById('silenceProgress').style.width = "0%";
            document.getElementById('silenceCountdown').innerText = "0.0";
        }
        
        function disconnectWebSocket() {
            if (ws) {
                isRecording = false;
                ws.close();
                document.getElementById('status').innerText = "已断开连接";
                document.getElementById('toggleBtn').disabled = true;
                document.getElementById('connectBtn').disabled = false;
                document.getElementById('disconnectBtn').disabled = true;
                
                // 重置静音进度条
                resetSilenceProgress();
                
                addLogEntry("手动断开WebSocket连接", "orange");
            }
        }
        
        function toggleRecording() {
            const button = document.getElementById('toggleBtn');
            
            if (ws && ws.readyState === WebSocket.OPEN) {
                if (button.innerText === "开始录音") {
                    // 发送开始录音指令
                    const langSym = document.getElementById('langSym').value;
                    const autoSilenceDetection = document.getElementById('autoSilenceDetection').value === "true";
                    
                    const startMessage = {
                        action: 'start',
                        lang_sym: langSym,
                        auto_silence_detection: autoSilenceDetection
                    };
                    
                    ws.send(JSON.stringify(startMessage));
                    addLogEntry("发送: " + JSON.stringify(startMessage), "green");
                    
                    isRecording = true;
                    button.innerText = "停止录音";
                    document.getElementById('resultText').value = "录音中...";
                    
                    // 根据自动静音检测设置显示或隐藏静音倒计时
                    document.getElementById('silenceContainer').style.display = autoSilenceDetection ? "block" : "none";
                    
                    // 重置静音进度条
                    resetSilenceProgress();
                } else {
                    // 发送停止录音指令
                    const stopMessage = {
                        action: 'stop'
                    };
                    
                    ws.send(JSON.stringify(stopMessage));
                    addLogEntry("发送: " + JSON.stringify(stopMessage), "green");
                    
                    isRecording = false;
                    button.innerText = "开始录音";
                    
                    // 重置静音进度条
                    resetSilenceProgress();
                }
            }
        }
        
        function updateVolumeIndicator(data) {
            const volumeIndicator = document.getElementById('volumeIndicator');
            const volume = data.volume || 0;
            const isSpeaking = data.is_speaking;
            
            // 根据音量设置音量指示器的显示
            volumeIndicator.innerText = `音量: ${volume.toFixed(2)}`;
            volumeIndicator.className = isSpeaking ? 'speaking' : 'not-speaking';
        }
        
        function updateHistoryDisplay() {
            const historyContainer = document.getElementById('historyContainer');
            historyContainer.innerHTML = '';
            
            // 显示最近的5条记录
            const recentHistory = recognitionHistory.slice(-5);
            
            for (const item of recentHistory) {
                const historyItem = document.createElement('div');
                historyItem.className = 'history-item';
                
                let infoText = `<strong>${item.time}</strong>: ${item.text}`;
                if (item.inference_time) {
                    infoText += ` (推理时间: ${item.inference_time.toFixed(3)}秒)`;
                }
                
                historyItem.innerHTML = infoText;
                historyContainer.appendChild(historyItem);
            }
            
            // 滚动到底部
            historyContainer.scrollTop = historyContainer.scrollHeight;
        }
        
        function clearHistory() {
            recognitionHistory = [];
            updateHistoryDisplay();
        }
        
        function addLogEntry(message, color = "black") {
            const logContainer = document.getElementById('logContainer');
            const logEntry = document.createElement('div');
            logEntry.className = 'log-entry';
            logEntry.style.color = color;
            
            const timestamp = new Date().toLocaleTimeString();
            logEntry.innerText = `[${timestamp}] ${message}`;
            
            logContainer.appendChild(logEntry);
            logContainer.scrollTop = logContainer.scrollHeight;
        }
        
        function clearLog() {
            document.getElementById('logContainer').innerHTML = '';
        }
        
        function copyResult() {
            const resultText = document.getElementById('resultText');
            resultText.select();
            document.execCommand('copy');
            addLogEntry("已复制识别结果到剪贴板", "green");
        }
        
        // 页面加载时的初始化
        window.onload = function() {
            addLogEntry("页面已加载，正在自动连接到服务器...", "black");
            
            // 自动连接WebSocket服务器
            setTimeout(function() {
                connectWebSocket();
            }, 500);
        };
        
        // 页面关闭时关闭WebSocket连接
        window.onbeforeunload = function() {
            if (ws) {
                ws.close();
            }
        };
    </script>
</body>
</html> 